// Copyright (C) Microsoft Corporation. All rights reserved.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Text;
using System.Globalization;
using System.Security.Permissions;

[assembly: SecurityPermission(SecurityAction.RequestMinimum)]

namespace Microsoft.DDSolutions.SqlReportServer
{
    /// <summary>
    /// The Proxy which implements the functionality to access all the SQL Reporting Services
    /// </summary>
    public class ReportingServiceProxy : IDisposable, IReportService
    {
        private ReportServer.ReportingService SQLReportingServices = new Microsoft.DDSolutions.SqlReportServer.ReportServer.ReportingService();
        //  private string hostServer;
        bool disposed;

        /// <summary>
        /// Indicates whether we are dealing with server-level permission (IsProjectSite=false) or project-level permission (IsProjectSite=true)
        /// </summary>
        public bool IsProjectSite
        {
            get;
            set;
        }

        /// <summary>
        /// Creates the report service proxy
        /// </summary>
        /// <param name="reportService">The full URI of the service (from the Registration service)</param>
        public ReportingServiceProxy(string reportService)
        {
            //    hostServer = reportService;
            SQLReportingServices.Url = reportService;
            //SQLReportingServices.Url = "https://" + hostServer + "/ReportServer/reportservice.asmx";
            SQLReportingServices.UseDefaultCredentials = true;
        }

        /// <summary>
        ///  Dispose method to dispose the Web Service resource.
        /// </summary>
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing)
        {
            if (disposed == true) return;

            if (disposing)
            {
                SQLReportingServices.Dispose();
                disposed = true;
            }
        }


        /// <summary>
        ///   Gets all the role names defined on the Server
        /// </summary>
        /// <returns>ReadOnlyCollection of string</returns>
        public ReadOnlyCollection<string> Roles
        {
            get
            {
                ReportServer.Role[] rsRoles = SQLReportingServices.ListRoles();
                Collection<string> allRoles = new Collection<string>();

                foreach (ReportServer.Role rsRole in rsRoles)
                {
                    allRoles.Add(rsRole.Name);
                }

                return new ReadOnlyCollection<string>(allRoles);
            }
        }

        /// <summary>
        ///  Get all roles assigned to a given user. Exceptions are thrown by the 
        ///  web service calls that needs to be caught by the caller.
        /// </summary>
        /// <param name="userName">Name of the User</param>
        /// <param name="nodeName">Name of the project or folder</param>
        /// <returns>ReadOnlyCollection of string</returns>
        public ReadOnlyCollection<string> GetUserRoles(string userName, string nodeName)
        {
            ValidateUserProject(userName, nodeName);

            ReportServer.Policy[] policies = GetPolicies(nodeName);

            Collection<string> userRoles = new Collection<string>();

            foreach (ReportServer.Policy rsUser in policies)
            {
                if (String.Compare(rsUser.GroupUserName, userName, true, CultureInfo.CurrentCulture) != 0)
                    continue;

                foreach (ReportServer.Role rsRole in rsUser.Roles)
                    userRoles.Add(rsRole.Name);

                break;
            }

            return new ReadOnlyCollection<string>(userRoles);
        }

        /// <summary>
        ///  Is used to find if a role exist on the Reporting Services Server
        /// </summary>
        /// <param name="roleName">Name of the role</param>
        /// <param name="nodeName">Name of the project or folder</param>
        /// <returns>True if the role exist on the server or False if it doesn't</returns>
        public bool IsRoleExist(string roleName)
        {
            if (roleName == null)
                throw new ArgumentNullException("roleName");

            ReadOnlyCollection<string> roles = Roles;

            return roles.Contains(roleName);
        }

        /// <summary>
        ///    Add a user with given role to a project or folder as its called on the 
        /// reporting services site. The exceptions needs to be caught by the caller
        /// as SOAP exceptions would be thrown by the web service calls.
        /// </summary>
        /// <param name="userName">Name of the User</param>
        /// <param name="nodeName">Project or Folder to which its getting added</param>
        /// <param name="Role">Role assigned to the user</param>
        public void AddUser(string userName, string nodeName, string roleName)
        {
            ValidateUserProjectRole(userName, nodeName, roleName);
            bool foundUser = false;


            ReportServer.Policy[] policies = GetPolicies(nodeName);
            System.Collections.ArrayList policylist = new System.Collections.ArrayList(policies);

            /// New role for the user.
            ReportServer.Role role = new ReportServer.Role();
            role.Name = roleName;

            /// Needed in case this is a brand new user.
            /// Its moved here so that GetType could use it below.
            ReportServer.Policy uPolicy = new Microsoft.DDSolutions.SqlReportServer.ReportServer.Policy();

            foreach (ReportServer.Policy userPolicy in policylist)
            {
                /// case where the user already exist on the server and you are just adding a new role
                /// to that user.
                if (String.Compare(userPolicy.GroupUserName, userName, StringComparison.CurrentCultureIgnoreCase) == 0)
                {
                    /// Get the current user role list.
                    System.Collections.ArrayList rolelist = new System.Collections.ArrayList(userPolicy.Roles);

                    /// rolelist.Contains(role) doesn't return true if exist. 
                    /// Looks like they have not implemented the == operator.
                    /// So have to use foreach.
                    foreach (ReportServer.Role r in rolelist)
                    {
                        if (String.Compare(r.Name, roleName, StringComparison.CurrentCultureIgnoreCase) == 0)
                            return;
                    }

                    /// Add the new role 
                    rolelist.Add(role);

                    /// Reset the role list to the userPolicy
                    userPolicy.Roles = (ReportServer.Role[])rolelist.ToArray(role.GetType());

                    foundUser = true;
                    break;
                }
            }

            ///Case where the user doesn't exist yet on this site with any role
            if (foundUser == false)
            {
                uPolicy.GroupUserName = userName;

                /// Create an role list with one role.
                uPolicy.Roles = new Microsoft.DDSolutions.SqlReportServer.ReportServer.Role[0];

                /// Get the current user role list.
                System.Collections.ArrayList rolelist = new System.Collections.ArrayList(uPolicy.Roles);

                /// Add the new role 
                rolelist.Add(role);

                uPolicy.Roles = (ReportServer.Role[])rolelist.ToArray(role.GetType());

                /// Add policy to policy list.
                policylist.Add(uPolicy);
            }

            /// Update the policy list on the server
            ReportServer.Policy[] newpolicies = (ReportServer.Policy[])policylist.ToArray(uPolicy.GetType());
            SetPolicies(nodeName, newpolicies);
        }

        /// <summary>
        ///  This removes the user from the Reporting Services project site.The exceptions needs to be caught by the caller
        /// as SOAP exceptions would be thrown by the web service calls. The exception denotes a failure.
        /// </summary>
        /// <param name="userName">Name of the user</param>
        /// <param name="nodeName">Name of the project or folder</param>
        public void RemoveUser(string userName, string nodeName)
        {
            ValidateUserProject(userName, nodeName);


            ReportServer.Policy[] policies = GetPolicies(nodeName);
            if (policies.Length > 0)
            {
                System.Collections.ArrayList policylist = new System.Collections.ArrayList(policies);

                foreach (ReportServer.Policy userPolicy in policies)
                {
                    if (String.Compare(userPolicy.GroupUserName, userName, StringComparison.CurrentCultureIgnoreCase) == 0)
                    {
                        policylist.Remove(userPolicy);
                        ReportServer.Policy[] newpolicies = (ReportServer.Policy[])policylist.ToArray(policies[0].GetType());
                        SetPolicies(nodeName, newpolicies);
                        break;
                    }
                }
            }
        }

        /// <summary>
        ///   Remove the user from an assigned role for the given project or folder. The underlying web service
        /// call will throw a SOAP exception which denotes failure to remove user from role. It needs to be
        /// caught by the caller as its the caller who needs to decide what to do with that exception.
        /// </summary>
        /// <param name="userName">User Name</param>
        /// <param name="nodeName">Project or Folder Name</param>
        /// <param name="roleName">Role name</param>
        public void RemoveUserFromRole(string userName, string nodeName, string roleName)
        {
            ValidateUserProjectRole(userName, nodeName, roleName);

            ReportServer.Policy[] policies = GetPolicies(nodeName);

            if (policies.Length > 0)
            {
                System.Collections.ArrayList policylist = new System.Collections.ArrayList(policies);

                foreach (ReportServer.Policy userPolicy in policies)
                {

                    if (String.Compare(userPolicy.GroupUserName, userName, StringComparison.CurrentCultureIgnoreCase) == 0)
                    {
                        System.Collections.ArrayList rolelist = new System.Collections.ArrayList(userPolicy.Roles);

                        foreach (ReportServer.Role uRole in userPolicy.Roles)
                        {
                            if (String.Compare(uRole.Name, roleName, StringComparison.CurrentCultureIgnoreCase) == 0)
                            {
                                rolelist.Remove(uRole);

                                /// You could directly assign this to userPolicy.Roles, but helps in the debugger 
                                /// while debugging to compare the old roles and the new roles easily.
                                ReportServer.Role[] newroles = (ReportServer.Role[])rolelist.ToArray(uRole.GetType());

                                userPolicy.Roles = newroles;

                                /// No roles associated with this user anymore.
                                /// in which case remove the user from the site.
                                if (rolelist.Count < 1)
                                {
                                    policylist.Remove(userPolicy);
                                }
                                ReportServer.Policy[] newpolicies = (ReportServer.Policy[])policylist.ToArray(policies[0].GetType());
                                SetPolicies(nodeName, newpolicies);

                                break;
                            }
                        }
                    }
                }

            }
        }

        /// <summary>
        ///  Get policies that hold the user name, his roles for a given project.
        /// </summary>
        /// <param name="nodeName">Project or Folder Name</param>
        /// <returns>An array of ReportServer.Policy</returns>
        private ReportServer.Policy[] GetPolicies(string nodeName)
        {
            bool inheritParent;

            if (IsProjectSite)
            {
                /// Folder names on the site are refered to using the root path '/'
                nodeName = "/" + nodeName;
            }
            else
            {
                nodeName = "/";
            }
            ReportServer.Policy[] policies = SQLReportingServices.GetPolicies(nodeName, out inheritParent);

            return policies;
        }

        /// <summary>
        ///  Sets the policies for a given project or folder name on the report server
        /// </summary>
        /// <param name="nodeName"></param>
        /// <param name="policies"></param>
        private void SetPolicies(string nodeName, ReportServer.Policy[] policies)
        {
            if (IsProjectSite)
            {
                nodeName = "/" + nodeName;
            }
            else
            {
                nodeName = "/";
            }
            SQLReportingServices.SetPolicies(nodeName, policies);
        }

        /// <summary>
        ///  Validation helper function for often used parameters
        /// </summary>
        /// <param name="userName">User Name</param>
        /// <param name="nodeName">Project or Folder name</param>
        /// <param name="roleName">Role of the user</param>
        private static void ValidateUserProjectRole(string userName, string nodeName, string roleName)
        {
            ValidateUserProject(userName, nodeName);

            if (roleName == null)
                throw new ArgumentNullException("roleName");
        }

        /// <summary>
        ///  Validation helper function for often used parameters
        /// </summary>
        /// <param name="nodeName">Project or Folder name</param>
        /// <param name="roleName">Role of the user</param>
        private static void ValidateUserProject(string userName, string nodeName)
        {
            if (userName == null)
                throw new ArgumentNullException("userName");

            if (nodeName == null)
                throw new ArgumentNullException("nodeName");

        }

        /// <summary>
        ///   Verify if the Team Project has a connected Reporting Services site.
        /// </summary>
        /// <param name="nodeName">Project or Folder name</param>
        /// <returns>True or False depending on wether or not the site exists</returns>
        public bool VerifySiteExistance(string nodeName)
        {
            try
            {
                /// Make single call to list children of a node, could be any call really,
                /// /// if the site is dead we get an exception and return false.

                if (IsProjectSite)
                {
                    // Folder names on the site are refered to using the root path '/'
                    nodeName = "/" + nodeName;
                }
                else
                {
                    nodeName = "/";
                }
                
                SQLReportingServices.ListChildren(nodeName, false);
            }
            catch (System.Web.Services.Protocols.SoapException)
            {
                return false;
            }

            return true;
        }
    }
}
